package com.tiancheng.trade.settlement.service.settlement.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Image;
import com.itextpdf.layout.element.Paragraph;
import com.tiancheng.trade.commom.core.data.PageInfo;
import com.tiancheng.trade.commom.core.data.Result;
import com.tiancheng.trade.commom.core.utils.DateTimeUtil;
import com.tiancheng.trade.settlement.bo.*;
import com.tiancheng.trade.settlement.configuration.CmbConfiguration;
import com.tiancheng.trade.settlement.enums.OrderSourceEnum;
import com.tiancheng.trade.settlement.enums.SubOrderStatusEnum;
import com.tiancheng.trade.settlement.exception.BusinessException;
import com.tiancheng.trade.settlement.feign.FileUpload;
import com.tiancheng.trade.settlement.feign.dto.FilerUploadResult;
import com.tiancheng.trade.settlement.mapper.settlement.SSettleLettersMapper;
import com.tiancheng.trade.settlement.model.settlement.SSettleLetters;
import com.tiancheng.trade.settlement.model.settlement.SSettleLettersDetail;
import com.tiancheng.trade.settlement.service.settlement.ISRevenueDetailService;
import com.tiancheng.trade.settlement.service.settlement.ISSettleLettersDetailService;
import com.tiancheng.trade.settlement.service.settlement.ISSettleLettersService;
import com.tiancheng.trade.settlement.util.*;
import com.tiancheng.trade.settlement.vo.settlement.admin.SSettleLettersDetailQueryVo;
import com.tiancheng.trade.settlement.vo.settlement.admin.res.SSettleLettersDetailResVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.poi.ss.usermodel.Workbook;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.io.*;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import static com.tiancheng.trade.settlement.exception.error.BusinessErrorInfoEnum.*;

/**
 * @Auther: Likailun
 * @Date: 11:13 2021/5/8
 * @Description: 对账函service impl
 */
@Slf4j
@Service
public class SSettleLettersServiceImpl extends ServiceImpl<SSettleLettersMapper, SSettleLetters> implements ISSettleLettersService {
    public static final String LOCK_GENERATE_SETTLE_KEY = "GENERATE_SETTLE_LETTERS_LOCK";

    @Resource
    private ISRevenueDetailService revenueDetailService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private CmbConfiguration cmbConfiguration;
    @Resource
    private IdGenerateUtil idGenerateUtil;
    @Autowired
    private FileUpload fileUpload;
    @Resource
    private ISSettleLettersDetailService settleLettersDetailService;


    @Override
    public PageInfo<SSettleLetters> queryLit(SSettleLettersDetailQueryVo queryVo) {
        if (queryVo.getSettleEnd().until(queryVo.getSettleStart(), ChronoUnit.DAYS) > 31) {
            REVENUE_DATE_TOO_LONG.assertFail();
        }
        queryVo.buildStart();
        queryVo.setDataStatus(0);
        Integer total = this.getBaseMapper().queryPageTotal(queryVo);
        List<SSettleLetters> list = null;
        if (null != total && total > 0) {
            list = this.getBaseMapper().queryPage(queryVo);
        }
        return new PageInfo<>(list, new PageInfo.Pagination(queryVo.getCurrentPage(), queryVo.getPageSize(), total));
    }

    @Override
    public Result generateSettleLetters(SSettleLettersDetailQueryVo queryVo) {
        // 同时刻不允许有多个任务处理相同数据
        RLock lock = redissonClient.getLock(LOCK_GENERATE_SETTLE_KEY);
        boolean isLock = false;
        // 根目录路径
        String rootDirName = cmbConfiguration.getPaySavePath() + this.getRootDirName(queryVo);
        try {
            isLock = lock.tryLock(30L, TimeUnit.MINUTES);
            if (!isLock) {
                log.error("生成对账函异常，获取锁失败！");
                REVENUE_SETTLE_LETTERS_EXIST_TASK.assertFail();
                return Result.fail("生成对账函异常，获取锁失败，请稍后重试！");
            }
            SSettleLettersDetailQueryBO queryBo = SSettleLettersDetailQueryBO.build(queryVo);
            PageInfo<SSettleLettersDetailResVO> pageInfo = settleLettersDetailService.queryLit(queryBo);
            REVENUE_EXIST_SETTLE_LETTERS.assertIsTrue(pageInfo == null || pageInfo.getPagination().getTotal() < 1);
            List<SRevenueSettleLettersMerchantBo> merchantBos = revenueDetailService.summarySettleLettersMerchantInfo(queryBo);
            REVENUE_SETTLE_LETTERS_NULL_RECORD.assertIsTrue(!CollectionUtils.isEmpty(merchantBos));
            // 明细查询条件
            SSettleLettersDetailQueryVo queryConditionVO = ObjectConvertUtil.convert(queryVo, SSettleLettersDetailQueryVo.class).get();
            SSettleLettersDetailQueryBO queryConditionBO = SSettleLettersDetailQueryBO.build(queryConditionVO);
            // 根目录路径
            File rootFile = new File(rootDirName);
            if (!rootFile.exists()) {
                rootFile.mkdirs();
                log.info("创建文件夹：{}", rootFile.getName());
            }

            // 主对账函编号
            String mainSettleLetterCode = idGenerateUtil.getSettleLetterCode();
            Map<SRevenueSettleLettersMerchantBo, Workbook> workbooks = new HashMap<>();
            for (SRevenueSettleLettersMerchantBo merchantBo : merchantBos) {
                // 根据商户+场景纬度来生成对账函
                queryConditionBO.setMerchantCode(merchantBo.getMerchantCode());
                queryConditionBO.setOrderSource(merchantBo.getOrderSource());
                // 设置对账函编号
                merchantBo.setSettleLetterCode(mainSettleLetterCode);
                merchantBo.setSubSettleLetterCode(idGenerateUtil.getSettleLetterCode());
                merchantBo.setOrderSourceName(OrderSourceEnum.getDesc(merchantBo.getOrderSource()));
                //金额大写转化
                if (merchantBo.getProductCostTotal() > 0) {
                    merchantBo.setProductCostTotalCN(AmountUtil.convert(AmountUtil.getYUAN(merchantBo.getProductCostTotal()).toPlainString()));
                }
                if (merchantBo.getSubOrderAmountTotal() > 0) {
                    merchantBo.setSubOrderAmountTotalCN(AmountUtil.convert(AmountUtil.getYUAN(merchantBo.getSubOrderAmountTotal()).toPlainString()));
                }
                // 对账函明细
                List<SRevenueSettleLettersMerchantDetailBo> list;
                List<SettleLettersExportBO> result = new ArrayList<>();
                long position = 0;
                do {
                    queryConditionBO.setPosition(position);
                    list = revenueDetailService.detailSettleLettersMerchantInfo(queryConditionBO);
                    if (!CollectionUtils.isEmpty(list)) {
                        list.forEach(item -> {
                            result.add(new SettleLettersExportBO(item.getSubOrderCreateDt(),
                                    item.getSubOrderId(),
                                    item.getPayFlowId(),
                                    item.getMerchantName(),
                                    item.getProductName(),
                                    AmountUtil.getYUAN(item.getSubOrderAmount()),
                                    AmountUtil.getYUAN(item.getProductCost()),
                                    SubOrderStatusEnum.getDesc(item.getSubOrderStatus()),
                                    item.getVerificationTime()
                            ));
                        });
                        position = result.size() - 1;
                    }
                } while (!CollectionUtils.isEmpty(list) && list.size() > 999);
                String fileName = String.format("%s-对账单明细.xlsx", merchantBo.getMerchantName());
                List<ExportBO> exportBoS = new ArrayList<>();
                String[] allTitleSummary = {"交易日期", "订单号", "支付单号", "商户名称", "订单名称", "订单金额", "结算金额", "订单状态", "核销日期"};
                ExportBO exportBoSummary = new ExportBO(fileName.replace(".xlsx", ""), null, allTitleSummary, result);
                exportBoS.add(exportBoSummary);
                workbooks.put(merchantBo, ExcelUtil.buildExcelWorkBook(exportBoS));
            }

            workbooks.forEach((bo, workbook) -> {
                try {
                    File merchantDir = new File(rootDirName + "/" + bo.getMerchantName());
                    if (!merchantDir.exists()) {
                        merchantDir.mkdirs();
                        log.info("创建文件夹：{}", merchantDir.getName());
                    }
                    // 生成商户文件夹
                    //zipOutputStream.putNextEntry(new ZipEntry(merchantNameDir));
                    // 生成商户-对账明细

                    ByteArrayOutputStream bos = new ByteArrayOutputStream();
                    workbook.write(bos);

                    FileOutputStream fileOutputStream = new FileOutputStream(String.format("%s/%s-%s-对账单-客户联.xlsx", merchantDir, bo.getMerchantName(), bo.getOrderSource()));
                    fileOutputStream.write(bos.toByteArray());
                    fileOutputStream.close();
                    bos.close();
                    // 生成商户-对账单-客户联
                    String customerPdfPath = this.writePdf(bo, "customer", queryVo);
                    //extracted(zipOutputStream, String.format("%s%s",bo.getMerchantName() + "/",customerPdfPath.substring(customerPdfPath.lastIndexOf("/")+1)),customerPdfPath);
                    //生成商户-内部联pdf文档
                    String platformPdfPath = this.writePdf(bo, "platform", queryVo);
                    //extracted(zipOutputStream, String.format("%s" ,platformPdfPath.substring(platformPdfPath.lastIndexOf("/"))),platformPdfPath);

                } catch (IOException e) {
                    e.printStackTrace();
                    REVENUE_SETTLE_LETTERS_MAKE_FILE_ERROR.assertFail();
                }
            });

            ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(rootDirName + ".zip"));
            this.compress(zipOutputStream, new File(rootDirName), "");
            zipOutputStream.flush();
            zipOutputStream.close();

            File zipSrc = new File(rootDirName + ".zip");
            FileInputStream input = new FileInputStream(zipSrc);

            //MultipartFile multipartFile = new MultipartFile(createFileItem(input, this.getRootDirName(queryVo) + ".zip"));
            //FilerUploadResult upload = fileUpload.upload(multipartFile, "AUTH_READ").getData();
            FilerUploadResult upload =new FilerUploadResult();
            upload.setUrl("http://127.0.0.1:8888/23424");
            if (null != upload) {
                log.info("上传文件到文件服务器返回：{}", JsonUtil.toJson(upload));
                // 保存对账函汇总记录
                SSettleLetters sSettleLetters = new SSettleLetters();
                sSettleLetters.setSettleLettersCode(mainSettleLetterCode);
                sSettleLetters.setDataStatus(0);
                sSettleLetters.setAdjustCount(0);
                sSettleLetters.setSettleStart(queryVo.getSettleStart());
                sSettleLetters.setSettleEnd(queryVo.getSettleEnd());
                sSettleLetters.setSettleLettersFile(upload.getUrl());
                sSettleLetters.setSettleLettersFileName(zipSrc.getName());
                // 保存对账函明细
                List<SSettleLettersDetail> details = new ArrayList<>();
                workbooks.forEach((bo, w) -> {
                    SSettleLettersDetail detail = ObjectConvertUtil.convert(sSettleLetters, SSettleLettersDetail.class).get();
                    detail.setSubSettleLettersCode(bo.getSubSettleLetterCode());
                    detail.setMerchantCode(bo.getMerchantCode());
                    detail.setMerchantName(bo.getMerchantName());
                    detail.setOrderSource(bo.getOrderSource());
                    detail.setTotalOrderAmount(bo.getSubOrderAmountTotal());
                    detail.setTotalPayAmount(bo.getPayAmountTotal());
                    detail.setTotalDiscount(bo.getDiscountTotal());
                    detail.setTotalRevenueAmount(bo.getRevenueAmountTotal());
                    detail.setTotalProductCost(bo.getProductCostTotal());
                    detail.setRevenueType(bo.getRevenueType());
                    detail.setDataStatus(0);
                    detail.setAdjustCount(0);
                    detail.setOrgCode(bo.getOrgCode());
                    detail.setLettersType(0);
                    detail.setScOrderCount(bo.getScOrderCountTotal());
                    details.add(detail);
                });
                this.getBaseMapper().insert(sSettleLetters);
                settleLettersDetailService.saveBatch(details);
                return Result.success("生成对账函成功！");
            }
            return Result.fail("生成对账函失败，上传对账文件到文件服务器异常！");
        } catch (BusinessException e) {
            log.error("生成对账函失败：{}", e.getMessage());
            return Result.fail("生成对账函失败！" + e.getMessage());
        } catch (Exception e) {
            log.error("生成对账函失败：{}", e.getMessage());
            return Result.fail("生成对账函失败！");
        } finally {
            File rootFile = new File(rootDirName);
            if (rootFile.exists()) {
                deleteFile(new File(rootDirName));
                log.info("删除临时文件夹：{}", rootFile.getName());
            }
            File rootZipFile = new File(rootDirName + ".zip");
            if (rootZipFile.exists()) {
                rootZipFile.delete();
                log.info("删除临时zip文件：{}", rootDirName + ".zip");
            }
            if (isLock) {
                lock.unlock();
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteBySettleLettersCode(String settleLettersCode) {
        LambdaQueryWrapper<SSettleLetters> condition = new LambdaQueryWrapper<>();
        SSettleLetters entity = new SSettleLetters();
        entity.setDataStatus(1);
        // 标记主记录失效
        this.getBaseMapper().update(entity, condition.eq(SSettleLetters::getSettleLettersCode, settleLettersCode));
        // 失效明细项
        settleLettersDetailService.deleteBySettleLettersCode(settleLettersCode);
    }

    @Transactional
    @Override
    public void deleteBySettleLettersCode(List<String> cods) {
        LambdaQueryWrapper<SSettleLetters> condition = new LambdaQueryWrapper<>();
        SSettleLetters entity = new SSettleLetters();
        entity.setDataStatus(1);
        // 标记主记录失效
        this.getBaseMapper().update(entity, condition.in(SSettleLetters::getSettleLettersCode, cods));
        // 失效明细项
        settleLettersDetailService.deleteBySettleLettersCode(cods);
    }

    @Override
    public boolean isExistGenerateTask() {
        RLock lock = redissonClient.getLock(LOCK_GENERATE_SETTLE_KEY);
        return lock.isLocked();
    }

    @Override
    public void updateAdjustCount(String settleLettersCode, Integer adjustCount) {
        this.getBaseMapper().updateAdjustCount(settleLettersCode, adjustCount);
    }

    public void compress(ZipOutputStream out, File sourceFile, String base) throws Exception {
        //如果路径为目录（文件夹）
        if (sourceFile.isDirectory()) {
            //取出文件夹中的文件（或子文件夹）
            File[] flist = sourceFile.listFiles();
            //如果文件夹为空，则只需在目的地zip文件中写入一个目录进入点
            if (flist.length == 0) {
                out.putNextEntry(new ZipEntry(base + File.separator));
            } else {
                //如果文件夹不为空，则递归调用compress,文件夹中的每一个文件（或文件夹）进行压缩
                for (int i = 0; i < flist.length; i++) {
                    compress(out, flist[i], base + File.separator + flist[i].getName());
                }
            }
        } else {
            out.putNextEntry(new ZipEntry(base));
            FileInputStream fos = new FileInputStream(sourceFile);
            BufferedInputStream bis = new BufferedInputStream(fos);
            int len;

            byte[] buf = new byte[1024];
            while ((len = bis.read(buf, 0, 1024)) != -1) {
                out.write(buf, 0, len);
            }
            bis.close();
            fos.close();
        }
    }

    private void deleteFile(File file) {
        //判断文件不为null或文件目录存在
        if (file == null || !file.exists()) {
            log.error("文件删除失败,请检查文件路径是否正确");
            return;
        }
        //取得这个目录下的所有子文件对象
        File[] files = file.listFiles();
        //遍历该目录下的文件对象
        for (File f : files) {
            //打印文件名
            String name = file.getName();
            //判断子目录是否存在子目录,如果是文件则删除
            if (f.isDirectory()) {
                deleteFile(f);
            } else {
                f.delete();
            }
        }
        //删除空文件夹  for循环已经把上一层节点的目录清空。
        file.delete();
    }

    /**
     * FileItem类对象创建
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return FileItem
     */
    public static FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        OutputStream os = null;
        //使用输出流输出输入流的字节
        try {
            os = item.getOutputStream();
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (IOException e) {
            log.error("Stream copy exception", e);
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);

                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    log.error("Stream close exception", e);
                }
            }
        }

        return item;
    }

    /**
     * 写入
     *
     * @param zipOutputStream
     * @param targetPath      目标路径
     * @param sourcePath      原路径
     * @throws IOException
     */
    private void extracted(ZipOutputStream zipOutputStream, String targetPath, String sourcePath) throws IOException {
        ByteArrayOutputStream customerPdfBos = null;
        try {
            log.info("生成pdf文件：{}", targetPath);
            customerPdfBos = new ByteArrayOutputStream();
            zipOutputStream.putNextEntry(new ZipEntry(targetPath));
            InputStream inputStream = new FileInputStream(sourcePath);
            BufferedInputStream bis = new BufferedInputStream(inputStream);
            byte[] bytes = new byte[1024];
            int len = -1;
            while ((len = bis.read(bytes)) != -1) {
                customerPdfBos.write(bytes, 0, len);
            }
        } catch (Exception e) {
            log.error("写入pdf文件异常：{}", e.getMessage());
        } finally {
            if (customerPdfBos != null) {
                customerPdfBos.close();
            }
        }
    }

    /**
     * 生成pdf 文档
     *
     * @param merchantBo 商户统计结果
     * @param pdfType    pdf类型，customer：客户联，platform：平台联
     */
    public String writePdf(SRevenueSettleLettersMerchantBo merchantBo, String pdfType, SSettleLettersDetailQueryVo queryVo) {
        PdfDocument pdfDoc = null;
        try {
            String dirName = this.getRootDirName(queryVo);
            String rootDirName = cmbConfiguration.getPaySavePath() + dirName;
            String pdfPath = null;
            if ("customer".equals(pdfType)) {
                pdfPath = String.format("%s/%s/%s-客户联.pdf", rootDirName, merchantBo.getMerchantName(), merchantBo.getSubSettleLetterCode());
                pdfDoc = new PdfDocument(new PdfReader("/static/temp_customer.pdf"), new PdfWriter(pdfPath));
                log.info("读取到pdf模版文件,getNumberOfPdfObjects：{}", pdfDoc.getNumberOfPdfObjects());
            } else if ("platform".equals(pdfType)) {
                pdfPath = String.format("%s/%s-内部联.pdf", rootDirName, merchantBo.getSubSettleLetterCode());
                pdfDoc = new PdfDocument(new PdfReader("/static/temp_platform.pdf"), new PdfWriter(pdfPath));
            }
            PdfAcroForm pdfAcroForm = PdfAcroForm.getAcroForm(pdfDoc, true);
            //原始数据(待填充到PDF文件中的数据)

            Map<String, Object> formDatas = JSONUtil.toBean(JSONUtil.toJsonStr(merchantBo), HashMap.class);
            formDatas.put("settleLetterCode", merchantBo.getSubSettleLetterCode());
            //获取模板文件中的全量字段
            Map<String, PdfFormField> formFields = pdfAcroForm.getFormFields();
            formFields.forEach((key, value) -> {
                try {
                    log.info("读取pdf字段名称：{}", key);
                    //获取到模板中的某个字段
                    PdfFormField agreementId = formFields.get(key);
                    //处理中文乱码
                    if (formFields.containsKey(key) && null != agreementId) {
                        PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
                        //为模板中的某个字段设置值
                        Object filedValue = formDatas.get(key);
                        if ("orderSource".equals(key) && null != filedValue) {
                            // 场景
                            agreementId.setValue(OrderSourceEnum.getDesc(filedValue.toString()), font, 10);
                        } else if ("settleCycle".equals(key)) {
                            // 结算周期
                            agreementId.setValue(dirName.replace("对账函", ""), font, 6);
                        } else if ("createDate".equals(key)) {
                            String format = DateTimeUtil.format(LocalDateTime.now(), "yyyy 年 MM 月 dd 日");
                            agreementId.setValue(format, font, 10);
                        } else {
                            if (formDatas.get(key) instanceof Integer) {
                                Integer number = (Integer) formDatas.get(key);
                                if (null != number && number > 0) {
                                    agreementId.setValue(AmountUtil.getYUAN(number).toPlainString() + "元", font, 10);
                                }
                            } else if (null != filedValue) {
                                agreementId.setValue(filedValue.toString(), font, 10);
                            }
                        }
                    } else {
                        log.warn("未知的pdf key：{}", key);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    log.info("设置pdf字段值异常：{}", e.getMessage());
                }
            });
            //设置生成表单不可编辑，注意itext的版本，在7.0.0版本报错
            pdfAcroForm.flattenFields();
            this.doSeal(pdfDoc);

            return pdfPath;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("生成pdf异常:", e);
            return null;
        } finally {
            if (null != pdfDoc && !pdfDoc.isClosed()) {
                pdfDoc.close();
            }
        }
    }


    private void doSeal(PdfDocument doc) throws IOException {
        //插入图片
        Document document = new Document(doc);
        Image image = new Image(ImageDataFactory.create(SSettleLettersServiceImpl.class.getClassLoader().getResource("static/ynty_seal.png")));
        image.setWidth(100);
        image.setHeight(100);
        Paragraph paragraph = new Paragraph().add(image);
        paragraph.setPaddings(215, 0, 0, 210);
        document.add(paragraph);
    }

    /**
     * 对账函根路径名称
     *
     * @param queryVo
     * @return
     */
    private String getRootDirName(SSettleLettersDetailQueryVo queryVo) {
        String startDateStr = DateTimeUtil.format(queryVo.getSettleStart(), DateTimeUtil.yyyyMMdd);
        String endDateStr = DateTimeUtil.format(queryVo.getSettleEnd(), DateTimeUtil.yyyyMMdd);
        return String.format("%s至%s对账函", startDateStr, endDateStr);

    }
}
